﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net;
using System.Xml;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;

using ECCIT;

namespace ECCIT.Net.SAML2
{
    internal static class XmlSignature
    {
        // Fields
        private const string keyInfoXPath = "//*[local-name(.) = 'KeyInfo' and namespace-uri(.) = 'http://www.w3.org/2000/09/xmldsig#']";
        private const string signatureXPath = "*[local-name(.) = 'Signature' and namespace-uri(.) = 'http://www.w3.org/2000/09/xmldsig#']";
        private const string x509CertificateXPath = "//*[local-name(.) = 'X509Certificate' and namespace-uri(.) = 'http://www.w3.org/2000/09/xmldsig#']";

        // Methods
        static XmlSignature()
        {
            
        }

        public static XmlElement Generate(XmlElement xmlElement, string elementId, AsymmetricAlgorithm signingKey, SignedXml signedXml, string inclusiveNamespacesPrefixList)
        {
            return Generate(xmlElement, elementId, signingKey, new KeyInfo(), signedXml, inclusiveNamespacesPrefixList);
        }

        public static XmlElement Generate(XmlElement xmlElement, string elementId, AsymmetricAlgorithm signingKey, X509Certificate2 x509Certificate, SignedXml signedXml, string inclusiveNamespacesPrefixList)
        {
            X509Certificate2Collection certificates = null;
            try
            {
                certificates = new X509Certificate2Collection();
                certificates.Add(x509Certificate);
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to create X.509 certificate collection.", exception);
            }
            return Generate(xmlElement, elementId, signingKey, certificates, signedXml, inclusiveNamespacesPrefixList);
        }

        public static XmlElement Generate(XmlElement xmlElement, string elementId, AsymmetricAlgorithm signingKey, X509Certificate2Collection x509Certificates, SignedXml signedXml, string inclusiveNamespacesPrefixList)
        {
            KeyInfo keyInfo = null;
            try
            {
                keyInfo = new KeyInfo();
                KeyInfoX509Data clause = new KeyInfoX509Data();
                X509Certificate2Enumerator enumerator = x509Certificates.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    X509Certificate2 current = enumerator.Current;
                    clause.AddCertificate(current);
                }
                keyInfo.AddClause(clause);
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to create key info using X.509 certificate.", exception);
            }
            return Generate(xmlElement, elementId, signingKey, keyInfo, signedXml, inclusiveNamespacesPrefixList);
        }

        public static XmlElement Generate(XmlElement xmlElement, string elementId, AsymmetricAlgorithm signingKey, KeyInfo keyInfo, SignedXml signedXml, string inclusiveNamespacesPrefixList)
        {
            XmlElement xml;
            try
            {
                RemoveSignature(xmlElement);
                signedXml.SigningKey = signingKey;
                signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
                Reference reference = new Reference
                {
                    Uri = "#" + elementId
                };
                reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
                XmlDsigExcC14NTransform transform = new XmlDsigExcC14NTransform();
                if (!string.IsNullOrEmpty(inclusiveNamespacesPrefixList))
                {
                    transform.InclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList;
                }
                reference.AddTransform(transform);
                signedXml.AddReference(reference);
                signedXml.KeyInfo = keyInfo;
                signedXml.ComputeSignature();
                xml = signedXml.GetXml();
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to generate XML signature.", exception);
            }
            return xml;
        }

        public static XmlElement Generate(XmlElement xmlElement, string elementId, AsymmetricAlgorithm signingKey, KeyInfoX509Data keyInfoX509Data, SignedXml signedXml, string inclusiveNamespacesPrefixList)
        {
            KeyInfo keyInfo = null;
            try
            {
                keyInfo = new KeyInfo();
                keyInfo.AddClause(keyInfoX509Data);
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to create key info using X.509 data.", exception);
            }
            return Generate(xmlElement, elementId, signingKey, keyInfo, signedXml, inclusiveNamespacesPrefixList);
        }

        public static X509Certificate2 GetCertificate(XmlElement xmlElement)
        {
            X509Certificate2 certificate;
            try
            {
                XmlElement certificateElement = GetCertificateElement(xmlElement);
                if (certificateElement == null)
                {
                    return null;
                }
                string str = certificateElement.InnerText.Trim();
                if (string.IsNullOrEmpty(str))
                {
                    return null;
                }
                certificate = new X509Certificate2(Convert.FromBase64String(str));
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to extract X.509 certificate from XML.", exception);
            }
            return certificate;
        }

        public static XmlElement GetCertificateElement(XmlElement xmlElement)
        {
            XmlElement signatureElement = GetSignatureElement(xmlElement);
            if (signatureElement == null)
            {
                return null;
            }
            return (XmlElement)signatureElement.SelectSingleNode("//*[local-name(.) = 'X509Certificate' and namespace-uri(.) = 'http://www.w3.org/2000/09/xmldsig#']");
        }

        public static KeyInfo GetKeyInfo(XmlElement xmlElement)
        {
            KeyInfo info2;
            try
            {
                XmlElement keyInfoElement = GetKeyInfoElement(xmlElement);
                if (keyInfoElement == null)
                {
                    return null;
                }
                KeyInfo info = new KeyInfo();
                info.LoadXml(keyInfoElement);
                info2 = info;
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to extract key info from XML.", exception);
            }
            return info2;
        }

        public static XmlElement GetKeyInfoElement(XmlElement xmlElement)
        {
            XmlElement signatureElement = GetSignatureElement(xmlElement);
            if (signatureElement == null)
            {
                return null;
            }
            return (XmlElement)signatureElement.SelectSingleNode("//*[local-name(.) = 'KeyInfo' and namespace-uri(.) = 'http://www.w3.org/2000/09/xmldsig#']");
        }

        public static XmlElement GetSignatureElement(XmlElement xmlElement)
        {
            return (XmlElement)xmlElement.SelectSingleNode("*[local-name(.) = 'Signature' and namespace-uri(.) = 'http://www.w3.org/2000/09/xmldsig#']");
        }

        private static AsymmetricAlgorithm GetSigningKeyFromKeyInfo(KeyInfo keyInfo)
        {
            IEnumerator enumerator = keyInfo.GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (enumerator.Current is KeyInfoX509Data)
                {
                    KeyInfoX509Data current = (KeyInfoX509Data)enumerator.Current;
                    if (current.Certificates.Count != 0)
                    {
                        X509Certificate certificate = (X509Certificate)current.Certificates[0];
                        X509Certificate2 certificate2 = new X509Certificate2(certificate);
                        return certificate2.PublicKey.Key;
                    }
                }
                else
                {
                    if (enumerator.Current is RSAKeyValue)
                    {
                        RSAKeyValue value2 = (RSAKeyValue)enumerator.Current;
                        return value2.Key;
                    }
                    if (enumerator.Current is DSAKeyValue)
                    {
                        DSAKeyValue value3 = (DSAKeyValue)enumerator.Current;
                        return value3.Key;
                    }
                }
            }
            return null;
        }

        public static bool IsSigned(XmlElement xmlElement)
        {
            return (GetSignatureElement(xmlElement) != null);
        }

        public static XmlElement PrepareForSignatureVerification(XmlElement xmlElement)
        {
            IDictionary<string, string> namespacesInScope = xmlElement.CreateNavigator().GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
            foreach (string str in namespacesInScope.Keys)
            {
                if (string.IsNullOrEmpty(str))
                {
                    xmlElement.SetAttribute("xmlns", namespacesInScope[str]);
                }
                else
                {
                    xmlElement.SetAttribute(string.Format("xmlns:{0}", str), namespacesInScope[str]);
                }
            }
            XmlDocument document = XmlHelper.CreateDocument();
            document.AppendChild(document.ImportNode(xmlElement, true));
            return document.DocumentElement;
        }

        public static void RemoveSignature(XmlElement xmlElement)
        {
            XmlElement signatureElement = GetSignatureElement(xmlElement);
            if (signatureElement != null)
            {
                xmlElement.RemoveChild(signatureElement);
            }
        }

        public static bool Verify(XmlElement xmlElement, SignedXml signedXml)
        {
            return Verify(xmlElement, (AsymmetricAlgorithm)null, signedXml);
        }

        public static bool Verify(XmlElement xmlElement, AsymmetricAlgorithm signingKey, SignedXml signedXml)
        {
            bool flag2;
            try
            {
                XmlElement signatureElement = GetSignatureElement(xmlElement);
                if (signatureElement == null)
                {
                    string message = "The XML does not contain a signature.";
                    throw new SAMLSignatureException(message);
                }
                signedXml.LoadXml(signatureElement);
                bool flag = false;
                if (signingKey != null)
                {
                    flag = signedXml.CheckSignature(signingKey);
                }
                else
                {
                    flag = signedXml.CheckSignature();
                }
                flag2 = flag;
            }
            catch (Exception exception)
            {
                throw new SAMLSignatureException("Failed to verify the XML signature.", exception);
            }
            return flag2;
        }

        public static bool Verify(XmlElement xmlElement, X509Certificate2 x509Certificate, SignedXml signedXml)
        {
            return Verify(xmlElement, (x509Certificate != null) ? x509Certificate.PublicKey.Key : null, signedXml);
        }

        public static bool Verify(XmlElement xmlElement, KeyInfo keyInfo, SignedXml signedXml)
        {
            AsymmetricAlgorithm signingKeyFromKeyInfo = GetSigningKeyFromKeyInfo(keyInfo);
            if (signingKeyFromKeyInfo == null)
            {
                throw new SAMLSignatureException("No signing key could be found in the key info.");
            }
            return Verify(xmlElement, signingKeyFromKeyInfo, signedXml);
        }

        public static bool Verify(XmlElement xmlElement, KeyInfoX509Data keyInfoX509Data, SignedXml signedXml)
        {
            if (keyInfoX509Data.Certificates.Count == 0)
            {
                throw new SAMLSignatureException("No X.509 certificates in key info.");
            }
            X509Certificate certificate = (X509Certificate)keyInfoX509Data.Certificates[0];
            X509Certificate2 certificate2 = new X509Certificate2(certificate);
            return Verify(xmlElement, certificate2, signedXml);
        }

        // Nested Types
        public static class ElementNames
        {
            // Fields
            public const string KeyInfo = "KeyInfo";
            public const string Signature = "Signature";
            public const string X509Certificate = "X509Certificate";
        }

        public static class NamespaceURIs
        {
            // Fields
            public const string XmlDSig = "http://www.w3.org/2000/09/xmldsig#";
        }

        public static class Prefixes
        {
            // Fields
            public const string DS = "ds";
        }
    }
}
